// Copyright (C) ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
//
// $Id: element.cpp 318 2009-03-02 14:03:21Z heavyzheng $
//

#ifndef ZZHENG_XML_SOURCE
#define ZZHENG_XML_SOURCE
#endif // ZZHENG_XML_SOURCE

#include <zzheng/xml/dom/libxml2/element.hpp>
#include <zzheng/xml/dom/libxml2/document.hpp>
#include <zzheng/xml/dom/libxml2/utility.hpp>

#include <zzheng/xml/dom/exception.hpp>
#include <zzheng/xml/dom/node_proxy.hpp>

#include <cassert>
#include <cstring>
#include <string>

namespace zzheng {
namespace xml {
namespace dom {
namespace libxml2_ {

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // constructor and destructor
    //

    element::element(xmlNode* px): child_node(px), children_(px), attributes_(px) {
        assert(raw()->type == XML_ELEMENT_NODE);
    }

    element::~element() {
        // Do nothing.
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // element information access
    //

    std::string element::uri() const {
        if (raw()->ns != 0 && raw()->ns->href != 0) {
            return to_chars(raw()->ns->href);
        } else {
            return std::string();
        }
    }

    std::string element::prefix() const {
        if (raw()->ns != 0 && raw()->ns->prefix != 0) {
            return to_chars(raw()->ns->prefix);
        } else {
            return std::string();
        }
    }

    std::string element::qname() const {
        std::string pre = prefix();
        if (pre.empty()) {
            return name();
        } else {
            return (pre + ":" + name());
        }
    }

    void element::set_name(const std::string& name) {
        // TODO: check if the name is a valid name.
        xmlNodeSetName(raw(), to_xml_chars(name.c_str()));
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // child access
    //

    basic_node_ptr<element> element::first_elem() {
        return basic_node_ptr<element>( const_cast<element*>(first_elem_()) );
    }

    basic_node_ptr<const element> element::first_elem() const {
        return basic_node_ptr<const element>( first_elem_() );
    }

    basic_node_ptr<element> element::elem(const std::string& name) {
        return basic_node_ptr<element>( const_cast<element*>(elem_(name, std::string())) );
    }

    basic_node_ptr<const element> element::elem(const std::string& name) const {
        return basic_node_ptr<const element>( elem_(name, std::string()) );
    }

    basic_node_ptr<element> element::elem(const std::string& name,
                                          const std::string& uri) {
        return basic_node_ptr<element>( const_cast<element*>(elem_(name, uri)) );
    }

    basic_node_ptr<const element> element::elem(const std::string& name,
                                                const std::string& uri) const {
        return basic_node_ptr<const element>( elem_(name, uri) );
    }

    child_node_list& element::children() {
        return children_;
    }

    const child_node_list& element::children() const {
        return children_;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // attribute access
    //

    std::string element::attr(const std::string& name) const {
        typedef attribute_map::const_iterator const_iterator;
        const_iterator i = attributes_.find(name);
        if (i != attributes_.end()) {
            return i->value();
        } else {
            return std::string();
        }
    }

    std::string element::attr(const std::string& name, const std::string& uri) const {
        typedef attribute_map::const_iterator const_iterator;
        const_iterator i = attributes_.find(name, uri);
        if (i != attributes_.end()) {
            return i->value();
        } else {
            return std::string();
        }
    }

    void element::set_attr(const std::string& name, const std::string& value) {
        attributes_.insert_update(make_attribute(name, value));
    }

    void element::set_attr(const std::string& name, const std::string& uri, const std::string& value) {
        // TODO: update node_proxy make_* signatures?
        attributes_.insert_update(make_attribute(uri, name, value));
    }

    attribute_map& element::attributes() {
        return attributes_;
    }

    const attribute_map& element::attributes() const {
        return attributes_;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // namespace manipulation
    //

    void element::set_uri(const std::string& uri) {
        xmlNs* ns = 0;
        if (!uri.empty()) {
            ns = declare_namespace_(uri);
        }
        xmlSetNs(raw(), ns);
    }

    std::string element::declare_namespace(const std::string& uri) {
        xmlNs* px = declare_namespace_(uri);
        assert(px != 0);
        return ( px->prefix != 0 ? to_chars(px->prefix) : std::string() );
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // private
    //

    element* element::clone(bool recursive) const {
        xmlNode* px = clone_raw(recursive);
        return static_cast<element*>(px->_private);
    }

    const element* element::first_elem_() const {
        const element* found = 0;
        for (const xmlNode* i = raw()->children; found == 0 && i != 0; i = i->next) {
            if (i->type == XML_ELEMENT_NODE) {
                found = static_cast<const element*>(i->_private);
            }
        }
        return found;
    }

    const element* element::elem_(const std::string& name, const std::string& uri) const {
        const element* found = 0;
        for (const xmlNode* i = raw()->children; found == 0 && i != 0; i = i->next) {
            if (i->type == XML_ELEMENT_NODE) {
                const element* child = static_cast<const element*>(i->_private);
                if (child->name() == name && child->uri() == uri) {
                    found = child;
                }
            }
        }
        return found;
    }

    xmlNs* element::declare_namespace_(const std::string& uri) {

        // TODO: check the namespace URI is valid.
        if (uri.empty()) {
            std::string errmsg = "Invalid namespace URI: " + uri;
            throw bad_dom_operation(errmsg.c_str());
        }

        // Check if a namespace with the same URI has already been declared.
        // Quoted from libxml2 documentation:
        //   (xmlSearchNsByHref) Search a namespace aliasing a given URI. Recurse on the parents
        //   until it finds the defined namespace or return NULL otherwise.
        xmlNs* px = xmlSearchNsByHref( raw()->doc, raw(), to_xml_chars(uri.c_str()) );

        // If the namespace is not found, get a unique namespace prefix for the specified URI from
        // the owner document, and create a new namespace declaration on this element.
        // Quoted from libxml2 documentation:
        //   (xmlNewNs) Creation of a new namespace. This function will refuse to create a
        //   namespace with a similar prefix than an existing one present on this node. We use
        //   href==NULL in the case of an element creation where the namespace was not defined.
        // Note: xmlNewNs changes xmlNode::nsDef but not xmlNode::ns, which means, the created
        // namespace is only declared on the element, but the element is not under the namespace.
        if (px == 0) {
            std::string prefix = doc().next_namespace_prefix();
            px = xmlNewNs( raw(), to_xml_chars(uri.c_str()), to_xml_chars(prefix.c_str()) );
            if (px == 0) {
                std::string errmsg = "Fail to declare namespace with URI " + uri
                                   + " and prefix " + prefix
                                   + ": xmlNewNs() returns null.";
                throw internal_dom_error(errmsg.c_str());
            }
        }

        // Return the libxml2 namespace.
        return px;
    }

} // namespace zzheng::xml::dom::libxml2_
} // namespace zzheng::xml::dom
} // namespace zzheng::xml
} // namespace zzheng





